<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>Snake game in PyGTK</title>
<link rel="stylesheet" href="/cfg/format.css" type="text/css">
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<meta name="keywords" content="Mono, Python, PyGTK, games, tutorial, Snake, programming, GUI, multiplatform">
<meta name="description" content="Snake game in PyGTK">
<meta name="language" content="en">
<meta name="author" content="Jan Bodnar">
<meta name="distribution" content="global">

<script type="text/javascript" src="/lib/jquery.js"></script>
<script type="text/javascript" src="/lib/common.js"></script>

</head>

<body>

<div class="container">

<div id="wide_ad" class="ltow">
<script type="text/javascript"><!--
google_ad_client = "pub-9706709751191532";
/* 160x600, August 2011 */
google_ad_slot = "2484182563";
google_ad_width = 160;
google_ad_height = 600;
//-->
</script>
<script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js">
</script>
</div> 

<div class="content">


<a href="/" title="Home">Home</a>&nbsp;
<a href="..">Contents</a>


<h1>Snake game in PyGTK</h1>


<p>
In this part of the PyGTK programming tutorial, we will create a Snake game clone. 
</p>

<script type="text/javascript"><!--
google_ad_client = "pub-9706709751191532";
/* NewSquare */
google_ad_slot = "0364418177";
google_ad_width = 300;
google_ad_height = 250;
//-->
</script> 
<script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js"> 
</script>

<h2>Snake game</h2>

<p>
<b>Snake</b> is an older classic video game. It was first created in late 70s. 
Later it was brought to PCs. In this game the player
controls a snake. The objective is to eat as many apples as possible. 
Each time the snake eats an apple, its body grows. The snake must avoid the 
walls and its own body. This game is sometimes called <b>Nibbles</b>. 
</p>


<h2>Development</h2>

<p>
The size of each of the joints of a snake is 10px. The snake is controlled 
with the cursor keys. Initially the snake has three joints. The game
starts immediately. If the game is finished, we display "Game Over"
message in the middle of the Board. 
</p>

<div class="codehead">snake.py</div>
<pre class="code">
#!/usr/bin/python

# ZetCode PyGTK tutorial 
#
# This is a simple snake game
# clone
#
# author: jan bodnar
# website: zetcode.com 
# last edited: February 2009

import sys
import gtk
import cairo
import random
import glib


WIDTH = 300
HEIGHT = 270
DOT_SIZE = 10
ALL_DOTS = WIDTH * HEIGHT / (DOT_SIZE * DOT_SIZE)
RAND_POS = 26

x = [0] * ALL_DOTS
y = [0] * ALL_DOTS


class Board(gtk.DrawingArea):

    def __init__(self):
        super(Board, self).__init__()

        self.modify_bg(gtk.STATE_NORMAL, gtk.gdk.Color(0, 0, 0))
        self.set_size_request(WIDTH, HEIGHT)

        self.connect("expose-event", self.expose)
 
        self.init_game()

    def on_timer(self):

        if self.inGame:
            self.check_apple()
            self.check_collision()
            self.move()
            self.queue_draw()
            return True
        else:
            return False
    
    def init_game(self):

        self.left = False
        self.right = True
        self.up = False
        self.down = False
        self.inGame = True
        self.dots = 3

        for i in range(self.dots):
            x[i] = 50 - i * 10
            y[i] = 50
        
        try:
            self.dot = cairo.ImageSurface.create_from_png("dot.png")
            self.head = cairo.ImageSurface.create_from_png("head.png")
            self.apple = cairo.ImageSurface.create_from_png("apple.png")
        except Exception, e:
            print e.message
            sys.exit(1)

        self.locate_apple()
        glib.timeout_add(100, self.on_timer)

        
        

    def expose(self, widget, event):
    
        cr = widget.window.cairo_create()

        if self.inGame:
            cr.set_source_rgb(0, 0, 0)
            cr.paint()

            cr.set_source_surface(self.apple, self.apple_x, self.apple_y)
            cr.paint()

            for z in range(self.dots):
                if (z == 0): 
                    cr.set_source_surface(self.head, x[z], y[z])
                    cr.paint()
                else:
                    cr.set_source_surface(self.dot, x[z], y[z])                 
                    cr.paint()
        else:
            self.game_over(cr)
             
    

    def game_over(self, cr):

        w = self.allocation.width / 2
        h = self.allocation.height / 2

        (x, y, width, height, dx, dy) = cr.text_extents("Game Over")

        cr.set_source_rgb(65535, 65535, 65535)
        cr.move_to(w - width/2, h)
        cr.show_text("Game Over")
        self.inGame = False
    


    def check_apple(self):

        if x[0] == self.apple_x and y[0] == self.apple_y: 
            self.dots = self.dots + 1
            self.locate_apple()
        
    
    def move(self):

        z = self.dots

        while z > 0:
            x[z] = x[(z - 1)]
            y[z] = y[(z - 1)]
            z = z - 1

        if self.left:
            x[0] -= DOT_SIZE

        if self.right: 
            x[0] += DOT_SIZE

        if self.up:
            y[0] -= DOT_SIZE

        if self.down:
            y[0] += DOT_SIZE
        
    

    def check_collision(self):

        z = self.dots
       
        while z > 0:
            if z > 4 and x[0] == x[z] and y[0] == y[z]:
                self.inGame = False
            z = z - 1

        if y[0] > HEIGHT - DOT_SIZE: 
            self.inGame = False
        
        if y[0] < 0:
            self.inGame = False
        
        if x[0] > WIDTH - DOT_SIZE:
            self.inGame = False

        if x[0] < 0:
            self.inGame = False
        

    def locate_apple(self):
    
        r = random.randint(0, RAND_POS)
        self.apple_x = r * DOT_SIZE
        r = random.randint(0, RAND_POS)
        self.apple_y = r * DOT_SIZE
   

    def on_key_down(self, event): 
    
        key = event.keyval

        if key == gtk.keysyms.Left and not self.right: 
            self.left = True
            self.up = False
            self.down = False
        

        if key == gtk.keysyms.Right and not self.left:
            self.right = True
            self.up = False
            self.down = False
        

        if key == gtk.keysyms.Up and not self.down:
            self.up = True
            self.right = False
            self.left = False
        

        if key == gtk.keysyms.Down and not self.up: 
            self.down = True
            self.right = False
            self.left = False


class Snake(gtk.Window):

    def __init__(self):
        super(Snake, self).__init__()
        
        self.set_title('Snake')
        self.set_size_request(WIDTH, HEIGHT)
        self.set_resizable(False)
        self.set_position(gtk.WIN_POS_CENTER)

        self.board = Board()
        self.connect("key-press-event", self.on_key_down)
        self.add(self.board)
        
        self.connect("destroy", gtk.main_quit)
        self.show_all()


    def on_key_down(self, widget, event): 
     
        key = event.keyval
        self.board.on_key_down(event)


Snake()
gtk.main()
</pre>


<p>
First we will define some globals used in our game. 
</p>

<p>
The <b class="keyword">WIDTH</b> and <b class="keyword">HEIGHT</b> constants determine 
the size of the Board. The <b class="keyword">DOT_SIZE</b> is the size of the apple and the dot
of the snake. The <b class="keyword">ALL_DOTS</b> constant defines the maximum number of 
possible dots on the Board.
The <b class="keyword">RAND_POS</b> constant is used to calculate a random position of an apple. 
The <b class="keyword">DELAY</b> constant determines the speed of the game.
</p>

<pre class="explanation">
 x = [0] * ALL_DOTS
 y = [0] * ALL_DOTS
</pre>

<p>
These two lists store x, y coordinates of all possible joints of a snake. 
</p>

<p>
The <b class="keyword">init_game()</b> method initializes variables, loads 
images and starts a timeout function.
</p>

<pre class="explanation">
 self.left = False
 self.right = True
 self.up = False
 self.down = False
 self.inGame = True
 self.dots = 3
</pre>

<p>
When the game starts, the snake has three joints. And it is heading to the right. 
</p>


<p>
In the <b class="keyword">move()</b> method we have the key algorithm of the game. 
To understand it, look at how the snakeis moving. You control the head of the snake. 
You can change its direction with the cursor keys. The rest of the joints move
one position up the chain. The second joint moves where the first was, 
the third joint where the second was etc. 
</p>

<pre class="explanation">
 while z > 0:
     x[z] = x[(z - 1)]
     y[z] = y[(z - 1)]
     z = z - 1
</pre>

<p>
This code moves the joints up the chain.
</p>

<pre class="explanation">
 if self.left:
     x[0] -= DOT_SIZE
</pre>

<p>
Move the head to the left.
</p>

<p>
In the <b class="keyword">checkCollision()</b> method, we determine if the snake has 
hit itself or one of the walls. 
</p>

<pre class="explanation">
 while z > 0:
     if z > 4 and x[0] == x[z] and y[0] == y[z]:
         self.inGame = False
     z = z - 1
</pre>

<p>
Finish the game, if the snake hits one of its joints with the head. 
</p>

<pre class="explanation">
 if y[0] > HEIGHT - DOT_SIZE: 
     self.inGame = False
</pre>

<p>
Finish the game, if the snake hits the bottom of the Board. 
</p>


<p>
The <b class="keyword">locate_apple()</b> method locates an apple randomly 
on the form.
</p>

<pre class="explanation">
 r = random.randint(0, RAND_POS)
</pre>

<p>
We get a random number from 0 to RAND_POS - 1.
</p>

<pre class="explanation">
 self.apple_x = r * DOT_SIZE
 ...
 self.apple_y = r * DOT_SIZE
</pre>

<p>
These line set the x, y coordinates of the apple
object. 
</p>


<pre class="explanation">
     self.connect("key-press-event", self.on_key_down)
     ...

 def on_key_down(self, widget, event): 
     
     key = event.keyval
     self.board.on_key_down(event)
</pre>

<p>
We catch the key press event in the Snake class, and delegate the
processing to the board object.
</p>

<p>
In the <b class="keyword">on_key_dow()</b> method of the Board class, we 
deternime which keys the player hit.
</p>

<pre class="explanation">
 if key == gtk.keysyms.Left and not self.right: 
     self.left = True
     self.up = False
     self.down = False
</pre>

<p>
If we hit the left cursor key, we set <b class="keyword">self.left</b> variable to 
True. This variable is used in the <b class="keyword">move()</b>
method to change coordinates of the snake object. Notice also, that
when the snake is heading to the right, we cannot turn immediately 
to the left. 
</p>


<br>
<img src="/img/gui/pygtk/snake.png" alt="Snake">
<div class="figure">Figure: Snake</div>

<hr class="btm">

<p>
This was the Snake computer game programmed using PyGTK programming library.
</p>

<br>
<div class="center"> 
<script type="text/javascript"><!--
google_ad_client = "pub-9706709751191532";
/* horizontal */
google_ad_slot = "1734478269";
google_ad_width = 468;
google_ad_height = 60;
//-->
</script> 
<script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js"> 
</script> 
</div> 

<br>


<div class="botNav, center">
<span class="botNavItem"><a href="/">Home</a></span> ‡ <span class="botNavItem"><a href="..">Contents</a></span> ‡
<span class="botNavItem"><a href="#">Top of Page</a></span>
</div>


<div class="footer">
<div class="signature">
<a href="/">ZetCode</a> last modified February 9, 2009  <span class="copyright">&copy; 2007 - 2012 Jan Bodnar</span>
</div>
</div>

</div>   <!-- content -->

</div>   <!-- container -->

</body>
</html>
